package ssiot

import (
	"encoding/json"
	"gitee.com/sansaniot/ssiot-gw-lib/ssiot/msgbus"
	"github.com/goinggo/mapstructure"
	"github.com/rs/zerolog/log"
	"gopkg.in/yaml.v3"
	"io/ioutil"
	"os"
	"path/filepath"
)

var (
	settings         = map[string]interface{}{}
	settingsFilename = "./config/settings.yml"
)

func LoadModSettings(mod string, v interface{}) {
	if s := settings[mod]; s != nil {
		if err := mapstructure.Decode(s, v); err != nil {
			log.Err(err).Msgf("decode settings mod error. %s", mod)
		}
	}
}

func SaveModSettingsToYamlAndPlat(mod string, v interface{}) {
	// 讲本地配置结构体转为map，写入s
	b, _ := json.Marshal(v)
	var s map[string]interface{}
	_ = json.Unmarshal(b, &s)

	settings[mod] = s
	SaveSettingsToYaml("")
	SaveSettingsToPlat(mod)
}

// SyncModSettings 将本地配置和平台获取的配置合并，然后同步到平台，本地配置中二级节点的配置，如果存在结构体中没有的，会同步到平台，只是结构体中无法读取
func SyncModSettings(mod string, v interface{}) {
	// 讲本地配置结构体转为map，写入s
	b, _ := json.Marshal(v)
	var s map[string]interface{}
	_ = json.Unmarshal(b, &s)

	// mem是从配置文件读取的本地配置map，如果配置文件中定义了结构体中没有的字段，mem会比结构体中多
	mem := settings[mod]
	if mem == nil {
		settings[mod] = s
		mem = s
	} else {
		mem = merge(mem.(map[string]interface{}), s)
	}

	ack := configGet(mod)
	if ack != nil && ack.Data != nil {
		if c := ack.Data.(map[string]interface{})[mod]; c != nil {
			mem = merge(mem.(map[string]interface{}), c.(map[string]interface{}))
		}
	}
	settings[mod] = mem
	SaveSettingsToYaml("")
	SaveSettingsToPlat(mod)
	if err := mapstructure.Decode(mem, v); err != nil {
		log.Err(err).Msgf("mapstructure decode settings error.")
	}
}

func LoadSettingsFromYaml(filename string) map[string]interface{} {
	if filename == "" {
		filename = settingsFilename
	} else {
		settingsFilename = filename
	}
	if data, err := ioutil.ReadFile(filename); err != nil {
		log.Err(err).Msgf("read settings file error. %s", filename)
	} else if err = yaml.Unmarshal(data, &settings); err != nil {
		log.Err(err).Msgf("unmarshal settings error.")
	}

	return settings
}

func LoadSettingsFromMessage(iot *msgbus.MsgIoT) {
	if iot == nil {
		return
	}
	if err := mapstructure.Decode(iot.Data, &settings); err != nil {
		log.Err(err).Msgf("mapstructure decode settings error.")
	}
}

func SaveSettingsToYaml(filename string) {
	if filename == "" {
		filename = settingsFilename
	} else {
		settingsFilename = filename
	}

	data, err := yaml.Marshal(&settings)
	if err != nil {
		log.Err(err).Msgf("marshal settings error.")
	}

	paths, _ := filepath.Split(filename)
	os.Mkdir(paths, os.ModePerm)

	if err := ioutil.WriteFile(filename, data, os.ModePerm); err != nil {
		log.Err(err).Msgf("write settings file error. %s", filename)
	}
}

func SaveSettingsToPlat(mod string) {
	iot := &msgbus.MsgIoT{
		Biz:  msgbus.CONFIG_SET,
		Data: map[string]interface{}{mod: settings[mod]},
	}
	iot.Publish()
}

func configGet(path string) *msgbus.MsgIoT {
	iot := new(msgbus.MsgIoT)
	iot.Biz = msgbus.CONFIG_GET
	iot.Data = &msgbus.MsgConfigGet{
		Path: path,
	}
	ack := iot.SyncPublish()
	return ack
}

func merge(dst, src map[string]interface{}) map[string]interface{} {
	for k, v := range src {
		if vv, ok := dst[k]; ok {
			switch vv := vv.(type) {
			case map[string]interface{}:
				v := v.(map[string]interface{})
				dst[k] = merge(vv, v)
			case []interface{}:
				v := v.([]interface{})
				dst[k] = v
			default:
				dst[k] = v
			}
		} else {
			dst[k] = v
		}
	}
	return dst
}
